package cn.webank.framework.seq.biz.service.impl;

import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import cn.webank.framework.exception.SysException;
import cn.webank.framework.seq.biz.service.SeqRedisService;
import cn.webank.framework.seq.integration.dao.SeqMemoryDAO;
import cn.webank.framework.seq.model.SequenceRange;
import cn.webank.framework.utils.WeBankContextUtil;

@Service("cn.webank.framework.seq.biz.service.SeqRedisService")
public class SeqRedisPojoService implements SeqRedisService {

	@Autowired
	@Qualifier("cn.webank.framework.seq.integration.dao.SeqMemoryDAO")
	private SeqMemoryDAO seqRedisDAO;

	private ConcurrentHashMap<String, SequenceRange> currentRangeMap = new ConcurrentHashMap<String, SequenceRange>();
	private static Lock sysSeqLock = new ReentrantLock();

	private final static long DEFAULT_RETRY_MILLS = 10000L;
	private final static int DEFAULT_STEP = 1000;

	private final static int DEFAULT_EFFECT_SECONDS = 24 * 3600 + 1;

	private int step = DEFAULT_STEP;
	private long retryTimeoutMills = DEFAULT_RETRY_MILLS;
	private int effectSeconds = DEFAULT_EFFECT_SECONDS;

	private boolean isNeedReset(SequenceRange seq, Date modifiedTime) {
		boolean result = false;
		Date currentDate = modifiedTime;
		Date lastModifiedDate = seq.getModifiedTime();
		if ((currentDate.after(lastModifiedDate))
				&& (!(DateFormatUtils.format(currentDate, "yyyy-MM-dd"))
						.equals(DateFormatUtils.format(lastModifiedDate,
								"yyyy-MM-dd")))) {// 每天重置为0
			// result
			// =
			// true;
		}
		return result;
	}

	public String nextValue(String namespace, String moduleId, Date currentDate) {

		SequenceRange sysCurrentRange = currentRangeMap.get(namespace
				+ moduleId);

		if ((sysCurrentRange == null)
				|| (isNeedReset(sysCurrentRange, currentDate))) {

			sysSeqLock.lock();
			try {
				if ((sysCurrentRange == null)
						|| (isNeedReset(sysCurrentRange, currentDate))) {
					if (!seqRedisDAO.isExistsSeq(namespace, moduleId)
							&& !seqRedisDAO.initSeq(namespace, moduleId,
									retryTimeoutMills, effectSeconds)) {
						throw new SysException("get Sequence timeout");

					}

					sysCurrentRange = seqRedisDAO.getSeq(namespace, moduleId,
							currentDate, step);
					currentRangeMap.put(namespace + moduleId, sysCurrentRange);
				}

			} finally {
				sysSeqLock.unlock();
			}
		}

		long value = sysCurrentRange.incrementAndGet();

		if (value == -1L) {
			sysSeqLock.lock();
			try {
				if (!seqRedisDAO.isExistsSeq(namespace, moduleId)
						&& !seqRedisDAO.initSeq(namespace, moduleId,
								retryTimeoutMills, effectSeconds)) {
					throw new SysException("get Sequence timeout");

				}

				sysCurrentRange = seqRedisDAO.getSeq(namespace, moduleId,
						currentDate, step);
				currentRangeMap.put(namespace + moduleId, sysCurrentRange);
			} finally {
				sysSeqLock.unlock();
			}
			value = sysCurrentRange.incrementAndGet();
		}

		if (value < 0L) {
			throw new SysException("Sequence value overflow.");
		}

		return "" + value;
	}

	/**
	 * @return the step
	 */
	public int getStep() {
		return step;
	}

	/**
	 * @param step
	 *            the step to set
	 */
	public void setStep(int step) {
		if (step > 0) {
			this.step = step;
		}
	}

	/**
	 * @return the retryTimeoutMills
	 */
	public long getRetryTimeoutMills() {
		return retryTimeoutMills;
	}

	/**
	 * @param retryTimeoutMills
	 *            the retryTimeoutMills to set
	 */
	public void setRetryTimeoutMills(long retryTimeoutMills) {
		if (retryTimeoutMills > 0) {
			this.retryTimeoutMills = retryTimeoutMills;
		}
	}

	/**
	 * @return the effectSeconds
	 */
	public int getEffectSeconds() {
		return effectSeconds;
	}

	/**
	 * @param effectSeconds
	 *            the effectSeconds to set
	 */
	public void setEffectSeconds(int effectSeconds) {
		if (effectSeconds > 0) {
			this.effectSeconds = effectSeconds;
		}
	}

	private String nextSeqNo(String namespace, String moduleId, String seqType,
			String dcnNo, Date currentDate) {
		String bizSeqNo = WeBankContextUtil.getBizSeqNo();
		if (!StringUtils.hasLength(bizSeqNo)) {
			String seq = nextValue(namespace, moduleId, currentDate);
			String date = DateFormatUtils.format(currentDate, "yyMMdd");
			bizSeqNo = date
					+ seqType
					+ dcnNo
					+ moduleId
					+ org.apache.commons.lang3.StringUtils
							.leftPad(seq, 18, '0');

			WeBankContextUtil.setBizSeqNo(bizSeqNo);
		}

		return bizSeqNo;
	}

	public String nextBizSeqNo(String namespace, String moduleId, String dcnNo,
			Date currentDate) {
		return this.nextSeqNo(namespace, moduleId, BIZ_SEQ, dcnNo, currentDate);
	}

	public String nextConsumerSeqNo(String namespace, String moduleId,
			String dcnNo, Date currentDate) {
		return this.nextSeqNo(namespace, moduleId, CONSUMER_SEQ, dcnNo,
				currentDate);
	}
}
